/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tencent.kuikly.compose.foundation.gestures

import com.tencent.kuikly.compose.animation.core.AnimationConstants.UnspecifiedTime
import com.tencent.kuikly.compose.animation.core.AnimationSpec
import com.tencent.kuikly.compose.animation.core.AnimationState
import com.tencent.kuikly.compose.animation.core.AnimationVector1D
import com.tencent.kuikly.compose.animation.core.VectorConverter
import androidx.compose.runtime.withFrameNanos
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.coroutineContext
import kotlin.math.absoluteValue
import kotlin.math.roundToLong

///**
// * Holds state for an [animation][animateToZero] that will continuously animate a float [value] to
// * zero.
// *
// * Unlike the standard [AnimationState], this class allows the value to be changed while the
// * animation is running. When that happens, the next frame will continue animating the new value
// * to zero as though the previous animation was interrupted and restarted with the new value. See
// * the docs on [animateToZero] for more information.
// *
// * An analogy for how this animation works is gravity – you can pick something up, and as soon as
// * you let it go it will start falling to the ground. If you catch it and raise it higher, it will
// * continue falling from the new height.
// *
// * Similar behavior could be achieved by using an [AnimationState] and creating a new copy and
// * launching a new coroutine to call `animateTo(0f)` every time the value changes. However, this
// * class doesn't require allocating a new state object and launching/cancelling a coroutine to
// * update the value, which makes for a more convenient API for this particular use case, and makes
// * it cheaper to update [value] on every frame.
// */
//internal class UpdatableAnimationState(animationSpec: AnimationSpec<Float>) {
//
//    private val vectorizedSpec = animationSpec.vectorize(Float.VectorConverter)
//    private var lastFrameTime = UnspecifiedTime
//    private var lastVelocity = ZeroVector
//    private var isRunning = false
//
//    /**
//     * The value to be animated. This property will be changed on every frame while [animateToZero]
//     * is running, and will be set to exactly 0f before it returns. Unlike [AnimationState], this
//     * property is mutable – it can be changed it any time during the animation, and the animation
//     * will continue running from the new value on the next frame.
//     *
//     * Simply setting this property will not start the animation – [animateToZero] must be manually
//     * invoked to kick off the animation, but once it's running it does not need to be called again
//     * when this property is changed, until the animation finishes.
//     */
//    var value: Float = 0f
//
//    /**
//     * Starts animating [value] to 0f. This function will suspend until [value] actually reaches
//     * 0f – e.g. if [value] is reset to a non-zero value on every frame, it will never return. When
//     * this function does return, [value] will have been set to exactly 0f.
//     *
//     * If this function is called more than once concurrently, it will throw.
//     *
//     * @param beforeFrame Called _inside_ the choreographer callback on every frame with the
//     * difference between the previous value and the new value. This corresponds to the typical
//     * frame callback used in the other animation APIs and [withFrameNanos]. It runs before
//     * composition, layout, and other passes for the frame.
//     * @param afterFrame Called _outside_ the choreographer callback for every frame, _after_ the
//     * composition and layout passes have finished running for that frame. This function allows the
//     * caller to update [value] based on any layout changes performed in [beforeFrame].
//     */
//    @OptIn(ExperimentalContracts::class)
//    suspend fun animateToZero(
//        beforeFrame: (valueDelta: Float) -> Unit,
//        afterFrame: () -> Unit,
//    ) {
//        contract { callsInPlace(beforeFrame) }
//        check(!isRunning) { "animateToZero called while previous animation is running" }
//
//        val durationScale = coroutineContext[MotionDurationScale]?.scaleFactor ?: 1f
//        isRunning = true
//
//        try {
//            // Don't rely on the animation's duration vs playtime to calculate completion since the
//            // value could be updated after each frame, and if that happens we need to continue
//            // running the animation.
//            while (!value.isZeroish()) {
//                withFrameNanos { frameTime ->
//                    if (lastFrameTime == UnspecifiedTime) {
//                        lastFrameTime = frameTime
//                    }
//
//                    val vectorizedCurrentValue = AnimationVector1D(value)
//                    val playTime = if (durationScale == 0f) {
//                        // The duration scale will be 0 when animations are disabled via a11y
//                        // settings or developer settings.
//                        vectorizedSpec.getDurationNanos(
//                            initialValue = AnimationVector1D(value),
//                            targetValue = ZeroVector,
//                            initialVelocity = lastVelocity
//                        )
//                    } else {
//                        ((frameTime - lastFrameTime) / durationScale).roundToLong()
//                    }
//                    val newValue = vectorizedSpec.getValueFromNanos(
//                        playTimeNanos = playTime,
//                        initialValue = vectorizedCurrentValue,
//                        targetValue = ZeroVector,
//                        initialVelocity = lastVelocity
//                    ).value
//                    lastVelocity = vectorizedSpec.getVelocityFromNanos(
//                        playTimeNanos = playTime,
//                        initialValue = vectorizedCurrentValue,
//                        targetValue = ZeroVector,
//                        initialVelocity = lastVelocity
//                    )
//                    lastFrameTime = frameTime
//
//                    val delta = value - newValue
//                    value = newValue
//                    beforeFrame(delta)
//                }
//                afterFrame()
//
//                if (durationScale == 0f) {
//                    // Never run more than one loop when animations are disabled.
//                    break
//                }
//            }
//
//            // The last iteration of the loop may have called block with a non-zero value due to
//            // the visibility threshold, so ensure it gets called one last time with actual zero.
//            if (value.absoluteValue != 0f) {
//                withFrameNanos {
//                    val delta = value
//                    // Update the value before invoking the callback so that the callback will see
//                    // the correct value if it looks at it.
//                    value = 0f
//                    beforeFrame(delta)
//                }
//                afterFrame()
//            }
//        } finally {
//            lastFrameTime = UnspecifiedTime
//            lastVelocity = ZeroVector
//            isRunning = false
//        }
//    }
//
//    private companion object {
//        const val VisibilityThreshold = 0.01f
//        val ZeroVector = AnimationVector1D(0f)
//
//        fun Float.isZeroish() = absoluteValue < VisibilityThreshold
//    }
//}
